/*
 * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../intdefs.h"
#include "../kv.h"
#include "../noreturn.h"
#include "../os.h"
#include "vec.h"

#ifdef _WIN32
#define fS "S"
#else
#define fS "s"
#endif

static noreturn die(const char *s) {
	fprintf(stderr, "mkgamedata: %s\n", s);
	exit(100);
}

/*
 * We keep the gamedata KV format as simple as possible. Default values are
 * specified as direct key-value pairs:
 *
 *   <varname> <expr>
 *
 * Game- or engine-specific values are set using blocks:
 *
 *   <varname> { <gametype> <expr> <gametype> <expr> ... [default <expr>] }
 *
 * The most complicated it can get is if conditionals are nested, which
 * basically translates directly into nested ifs:
 *   <varname> { <gametype> { <gametype> <expr> <gametype> <expr> } }
 *   [however many entries...]
 *
 * If that doesn't make sense, just look at one of the existing data files and
 * then it should be obvious. :^)
 *
 * Note: if `default` isn't given in a conditional block, that piece of gamedata
 * is considered unavailable and modules that use it won't get initialised/used
 * unless all the conditions are met.
 */
struct vec_ent VEC(struct ent *);
struct ent {
	const char *name; // (or condition tag, in a child node)
	const char *defexpr;
	struct vec_ent subents;
	struct ent *parent; // to back up a level during parse
};
// root only contains subents list but it's easier to use the same struct
static struct ent root = {0};

struct parsestate {
	const os_char *filename;
	struct kv_parser *parser;
	struct ent *curent; // current ent lol
	bool haddefault; // blegh;
};

static noreturn badparse(struct parsestate *state, const char *e) {
	fprintf(stderr, "mkgamedata: %" fS ":%d:%d: parse error: %s",
			state->filename, state->parser->line, state->parser->col, e);
	exit(1);
}

static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) {
	struct parsestate *state = ctxt;
	switch (type) {
		case KV_IDENT: case KV_IDENT_QUOTED:;
			if (len == 7 && !memcmp(p, "default", 7)) { // special case!
				if (state->curent == &root) {
					badparse(state, "unexpected default keyword at top level");
				}
				struct ent *e = state->curent;
				if (e->defexpr) {
					badparse(state, "multiple default keywords");
				}
				state->haddefault = true;
				break;
			}
			state->haddefault = false;
			char *k = malloc(len + 1);
			if (!k) die("couldn't allocate key string");
			// FIXME(?): should check and prevent duplicate keys probably!
			// need table.h or something to avoid O(n^2) :)
			memcpy(k, p, len); k[len] = '\0';
			struct ent *e = malloc(sizeof(*e));
			if (!e) die("couldn't allocate memory");
			e->name = k;
			e->defexpr = 0;
			e->subents = (struct vec_ent){0};
			if (!vec_push(&state->curent->subents, e)) {
				die("couldn't append to array");
			}
			e->parent = state->curent;
			state->curent = e;
			break;
		case KV_NEST_START:
			if (state->haddefault) badparse(state, "default cannot be a block");
			break;
		case KV_NEST_END:
			if (!state->curent->parent) {
				badparse(state, "unexpected closing brace");
			}
			state->curent = state->curent->parent;
			break;
		case KV_VAL: case KV_VAL_QUOTED:;
			char *s = malloc(len + 1);
			if (!s) die("couldn't allocate value string");
			memcpy(s, p, len); s[len] = '\0';
			state->curent->defexpr = s;
			if (!state->haddefault) {
				// a non-default value is just a node that itself only has a
				// default value.
				state->curent = state->curent->parent;
			}
			break;
		case KV_COND_PREFIX: case KV_COND_SUFFIX:
			badparse(state, "unexpected conditional");
	}
}

static inline noreturn diewrite(void) { die("couldn't write to file"); }

#define _doindent \
	for (int _indent = 0; _indent < indent; ++_indent) { \
		if (fputs("\t", out) == -1) diewrite(); \
	}
#define _(x) \
	if (fprintf(out, "%s\n", x) < 0) diewrite();
#define _i(x) _doindent _(x)
#define F(f, ...) \
	if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite();
#define Fi(...) _doindent F(__VA_ARGS__)
#define H() \
_( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \
_( "")

static void decls(FILE *out) {
	for (struct ent *const *pp = root.subents.data;
			pp - root.subents.data < root.subents.sz; ++pp) {
		if ((*pp)->defexpr) {
F( "#define has_%s true", (*pp)->name)
			if ((*pp)->subents.sz) {
F( "extern int %s;", (*pp)->name)
			}
			else {
F( "enum { %s = %s };", (*pp)->name, (*pp)->defexpr)
			}
		}
		else {
F( "extern bool has_%s;", (*pp)->name)
F( "extern int %s;", (*pp)->name)
		}
	}
}

static void inits(FILE *out, const char *var, struct vec_ent *v, bool needhas,
		int indent) {
	for (struct ent *const *pp = v->data; pp - v->data < v->sz; ++pp) {
Fi("if (GAMETYPE_MATCHES(%s)) {", (*pp)->name)
		if ((*pp)->defexpr) {
			if (needhas) {
Fi("	has_%s = true;", var);
			}
Fi("	%s = %s;", var, (*pp)->defexpr);
		}
		inits(out, var, &(*pp)->subents, needhas && !(*pp)->defexpr, indent + 1);
_i("}")
	}
}

static void defs(FILE *out) {
	for (struct ent *const *pp = root.subents.data;
			pp - root.subents.data < root.subents.sz; ++pp) {
		if ((*pp)->defexpr) {
			if ((*pp)->subents.sz) {
F( "int %s = %s;", (*pp)->name, (*pp)->defexpr);
			}
		}
		else {
F( "int %s;", (*pp)->name);
F( "bool has_%s = false;", (*pp)->name);
		}
	}
_( "")
_( "void gamedata_init(void) {")
	for (struct ent *const *pp = root.subents.data;
			pp - root.subents.data < root.subents.sz; ++pp) {
		inits(out, (*pp)->name, &(*pp)->subents, !(*pp)->defexpr, 1);
	}
_( "}")
}

int OS_MAIN(int argc, os_char *argv[]) {
	for (++argv; *argv; ++argv) {
		int fd = os_open(*argv, O_RDONLY);
		if (fd == -1) die("couldn't open file");
		struct kv_parser kv = {0};
		struct parsestate state = {*argv, &kv, &root};
		char buf[1024];
		int nread;
		while (nread = read(fd, buf, sizeof(buf))) {
			if (nread == -1) die("couldn't read file");
			if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep;
		}
		if (!kv_parser_done(&kv)) {
ep:			fprintf(stderr, "mkgamedata: %" fS ":%d:%d: bad syntax: %s\n",
					*argv, kv.line, kv.col, kv.errmsg);
			exit(1);
		}
		close(fd);
	}

	FILE *out = fopen(".build/include/gamedata.gen.h", "wb");
	if (!out) die("couldn't open gamedata.gen.h");
	H();
	decls(out);

	out = fopen(".build/include/gamedatainit.gen.h", "wb");
	if (!out) die("couldn't open gamedatainit.gen.h");
	H();
	defs(out);
	return 0;
}

// vi: sw=4 ts=4 noet tw=80 cc=80
